package org.gnomus.util;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLDecoder;
import java.security.AccessControlException;
import java.util.*;

public class classes {

    private static final Map<Class, Map<String, Field>> c2f = new HashMap<Class, Map<String, Field>>();
    private static final Map<Class, Set<Class<?>>> c2i = new HashMap<Class, Set<Class<?>>>();

    public static Map<String, Field> getFields(Object o) {
        synchronized (c2f) {
            Class c = o instanceof Class ? (Class)o : o.getClass();
            Map<String, Field> map = c2f.get(c);
            if (map == null) {
                map = new HashMap<String, Field>();
                Field[] fields = c.getDeclaredFields();
                for (Field f : fields) {
                    f.setAccessible(true);
                    int m = f.getModifiers();
                    boolean aes = f.isAccessible() && !f.isEnumConstant() && !f.isSynthetic();
                    boolean fst = Modifier.isFinal(m) || Modifier.isStatic(m) || Modifier.isTransient(m);
                    if (aes && !fst) {
                        map.put(f.getName(), f);
                    }
                }
                c2f.put(c, Collections.unmodifiableMap(map));
            }
            return map;
        }
    }

    public static void setField(Object ci, Field f, Object v) {
        try {
            f.set(ci, v);
        } catch (Throwable t) {
            throw new Error(t);
        }
    }

    public static Set<Class> getAllClasses(Class implementing) {
        Class c = getCallerClass();
        Set<Class> set = new TreeSet<Class>(new Comparator<Class>(){
            public int compare(Class o1, Class o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        String cn = c.getName();
        String cf = cn.replace('.', File.separatorChar);
        File f = resolveFile(c.getClassLoader(), cf + ".class");
        if (f != null) {
            String pn = packageOf(cn);
            File pf = f.getParentFile();
            for (;;) {
                if (pn == null || !io.isDirectory(pf.getParentFile())) {
                    break;
                }
                pn = packageOf(pn);
                pf = pf.getParentFile();
            }
            collectClasses(set, pf.getPath().length(), pf, implementing);
        }
        return set;
    }

    private static void collectClasses(Set<Class> set, int prefix, File dir, Class implementing) {
        try {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File f : files) {
                    if (io.isDirectory(f)) {
                        collectClasses(set, prefix, f, implementing);
                    } else if (f.getName().endsWith(".class")) {
                        String s = f.getPath().substring(prefix + 1);
                        s = s.substring(0, s.length() - ".class".length());
                        Class c = classes.forName(s.replace(File.separatorChar, '.'));
                        if (c != null &&
                                (implementing == null || getAllImplementedInterfaces(c).contains(implementing))) {
                            set.add(c);
                        }
                    }
                }
            }
        } catch (AccessControlException e) {
            /* ignore */
        }
    }

    private static Set<Class<?>> getAllImplementedInterfaces(Class c) {
        synchronized (c2i) {
            Set<Class<?>> s = c2i.get(c);
            if (s == null) {
                s = getAllImplementedInterfaces(c, new HashSet<Class<?>>());
                c2i.put(c, s);
            }
            return s;
        }
    }

    private static Set<Class<?>> getAllImplementedInterfaces(Class<?> c, Set<Class<?>> s) {
        if (c == null || c == Object.class) {
            return s;
        }
        for (Class<?> i : c.getInterfaces()) {
            if (i.isInterface()) {
                s.add(i);
            }
        }
        return getAllImplementedInterfaces(c.getSuperclass(), s);
    }

    private static File resolveFile(ClassLoader cl, String resource) {
        try {
            URL u = cl.getResource(resource);
            if (u != null) {
                File f = new File(URLDecoder.decode(u.getPath(), "UTF-8"));
                return io.exists(f) ? f : null;
            }
            return null;
        } catch (IOException e) {
            return null;
        }
    }

    private static Class getCallerClass() {
        return forName(Thread.currentThread().getStackTrace()[3].getClassName());
    }

    private static Class forName(String n) {
        try {
            return strings.isEmpty(n) ? null : Class.forName(n);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    private static String packageOf(String classname) {
        int ix = classname.lastIndexOf('.');
        return ix > 0 ? classname.substring(0, ix) : null;
    }

    public static Object newInstance(Class<?> c) {
        try {
            return c.newInstance();
        } catch (Throwable t) {
            throw new Error(t);
        }
    }
}
